/*
 * Reksio - Memory Map Editor
 * Copyright (C) 2023 CERN
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * In applying this licence, CERN does not waive the privileges and immunities
 * granted to it by virtue of its status as an Intergovernmental Organization or
 * submit itself to any jurisdiction.
 */

#include "nodesmodel.h"
#include "memorynode.h"
#include "memory_node_yaml.h"
#include "validatornode.h"
#include "attributesmodel.h"
#include "attributecontainervalidator.h"
#include "commands.h"
#include "utils.h"
#include "mainwindow.h"
#include <vector>
#include <QMimeData>
#include <QIODevice>
#include <QDataStream>
#include <QCoreApplication>
#include <QDebug>
#include <QBrush>
#include <QItemSelection>
#include <QFont>
#include <QIcon>
#include <QItemSelectionRange>

NodesModel::NodesModel(std::unique_ptr<MemoryNode> root, QUndoStack * undoStack, QObject* parent): QAbstractItemModel(parent), rootItem(std::move(root)), _undoStack(undoStack)
{
    connect(this, &NodesModel::dataChanged, this, QOverload<const QModelIndex &, const QModelIndex &, const QVector<int> &>::of(&NodesModel::validate));
    connect(this, &NodesModel::rowsInserted, this, QOverload<const QModelIndex &, int, int>::of(&NodesModel::validate));
    connect(this, &NodesModel::rowsRemoved, this, QOverload<const QModelIndex &, int, int>::of(&NodesModel::validate));
}

QVariant NodesModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    MemoryNode * node = getItem(index);

    if(role == InternalPointer)
    {
        return QVariant::fromValue(index.internalPointer());
    }
    else if(role == AttributeModelData)
    {
        return attribute_models[node];
    }

    if(role == Qt::FontRole)
    {
        QFont font;
        if(index.column() == 0)
            font.setBold(true);
        if(node->isSpecialSequence())
            font.setItalic(true);
        if(!node->isEditable())
            font.setUnderline(true);
        return font;
    }
    else if(role == Qt::BackgroundRole)
    {
        //qDebug() << "Background role!";
        //QBrush brush = QBrush(QColor(Qt::GlobalColor::red));
        //return brush;
    }
    else if (role == Qt::ForegroundRole)
    {
        QBrush invalid_brush = QBrush(QColor(Qt::GlobalColor::red));
        QBrush valid_brush = QBrush(QColor(Qt::GlobalColor::green));
        QBrush deprecated_brush = QBrush(QColor(Qt::GlobalColor::darkYellow));

        if(!node->isValid(true))
            return invalid_brush;
        if(node->hasDeprecated())
           return deprecated_brush;
    }
    else if(role == Qt::ToolTipRole)
    {
        QString tooltip_msg;
        if(!node->isEditable())
        {
            tooltip_msg += "This node is not editable!\n";
        }
        if(node->hasDeprecated())
        {
            if(node->isDeprecated())
            {
                tooltip_msg += "This node is deprecated!\n";
                auto deprecated_msg = node->getValidator()->getDeprecatedMessage();
                if(!deprecated_msg.empty())
                    tooltip_msg += QString::fromStdString(deprecated_msg) + "\n";
            }
            auto deprecated_attrs = node->getDeprecatedAttributes();
            auto deprecated_containers = node->getDeprecatedAttributeContainers();
            if(!deprecated_attrs.empty())
            {
                tooltip_msg += "This node contains deprecated attributes!\n";
                for(const auto& attr: deprecated_attrs)
                {
                    const QString& attr_location = QString::fromStdString(join(attr->getFullName(), "/"));
                    tooltip_msg += "Attribute " + attr_location + " is deprecated!\n";
                    auto attr_msg = attr->getValidator()->getDeprecatedMessage();
                    if(!attr_msg.empty())
                    {
                        tooltip_msg += QString::fromStdString(attr_msg) + "\n";
                    }
                }
            }
            if(!deprecated_containers.empty())
            {
                tooltip_msg += "This node contains deprecated attribute containers!\n";
                for(const auto& attr_container: deprecated_containers)
                {
                    const QString& attr_container_location = QString::fromStdString(join(attr_container->getFullName(), "/"));
                    tooltip_msg += "Attribute container " + attr_container_location + " is deprecated!\n";
                    auto attr_container_msg = attr_container->getValidator()->getDeprecatedMessage();
                    if(!attr_container_msg.empty())
                    {
                        tooltip_msg += QString::fromStdString(attr_container_msg) + "\n";
                    }
                }
            }


        }
        if(!node->isValid(true))
        {
            if(!node->pythonValidationResult() && node->hasPythonValidator())
            {
                tooltip_msg += QString::fromStdString(node->getValidator()->getPythonValidator()->getMessage()) + "\n";
            }
            // std::vector<std::pair<Attribute*, std::vector<std::shared_ptr<INodeRuleValidator>>>>
            const auto failed_validators = node->getFailedValidators();
            if(!failed_validators.empty())
                tooltip_msg += "Problem(s) with attributes:\n";
            for(const auto& failed_attr: failed_validators)
            {
                for (const auto& validator: failed_attr.second)
                {
                    tooltip_msg += QString::fromStdString(validator->getMessage()) + "\n";
                }
            }
            for(const auto& missing_attr: node->getMissingAttributes())
            {
                std::vector<std::string> full_name = missing_attr->getFullName();
                if(full_name.size() > 1)
                {
                    const QString& attr_location = QString::fromStdString(join(full_name, "/"));
                    tooltip_msg += "Missing attribute: " + attr_location + "\n";
                }
                else
                    tooltip_msg += "Missing attribute: " + QString::fromStdString(missing_attr->getName()) + "\n";

            }

        }
        return tooltip_msg;
    }
    else if(role == Qt::DecorationRole)
    {
        if(index.column() == 0)
        {
            if(rowCount(index))
                return QIcon(":/icons/icons/resources/16x16/box_closed.png");
            else
                return QIcon(":/icons/icons/resources/16x16/box_content.png");
        }
    }
    if (role != Qt::DisplayRole && role != Qt::EditRole)
        return QVariant();
    if(index.column() == 0)
        return QString::fromStdString(node->getType());
    if(index.column() == 1)
    {
        if(node->isSpecialSequence())
            return QVariant();
        return QString::fromStdString(node->getName());
    }
    return QVariant();
}

bool NodesModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if(role == CustomRoles::AttributeModelData)
    {
        MemoryNode * node = getItem(index);
        attribute_models[node] = value;
        return true;
    }
    return false;
}

QVariant NodesModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
    {
        if(section == 0)
            return QString("Type");
        if(section == 1)
            return QString("Name");
    }
    return QVariant();
}

QModelIndex NodesModel::index(int row, int column, const QModelIndex &parent) const
{
    if(!hasIndex(row, column, parent))
        return QModelIndex();
    MemoryNode* parentNode = getItem(parent);
    if(row >= parentNode->childCount())
        return QModelIndex();
    MemoryNode* childNode = parentNode->getChildren()[static_cast<size_t>(row)].get();
    return createIndex(row, column, childNode);
}

QModelIndex NodesModel::parent(const QModelIndex &index) const
{
    if(!index.isValid())
        return QModelIndex();
    MemoryNode* childNode = getItem(index);

    if(childNode == rootItem.get())
        return QModelIndex();
    MemoryNode* parentNode = childNode->getParent();
    if(!parentNode)
    {
        return QModelIndex();
    }
    if(parentNode == rootItem.get())
        return QModelIndex();
    return createIndex(parentNode->index(), 0, parentNode);
}

int NodesModel::rowCount(const QModelIndex &parent) const
{
    if(parent.column() > 0)
        return 0;
    MemoryNode * parentItem = getItem(parent);
    return parentItem->childCount();
}

int NodesModel::columnCount(const QModelIndex &/*parent*/) const
{
    return 2;
}

MemoryNode* NodesModel::getItem(const QModelIndex& index) const
{
    if(index.isValid())
    {
        MemoryNode * node = static_cast<MemoryNode*>(index.internalPointer());
        if (node)
            return node;
        throw std::runtime_error("getItem failed!");
    }
    return rootItem.get();
}

QUndoStack *NodesModel::undoStack() const
{
    return _undoStack;
}

AttributesModel *NodesModel::getAttributesModel(const QModelIndex &parent)
{
    const QModelIndex& parent_col_0 = parent.siblingAtColumn(0);
    QVariant data = parent_col_0.data(AttributeModelData);
    if(data.isNull())
    {
        AttributesModel * attrib_model = new AttributesModel(getItem(parent_col_0)->getAttributeContainer(), this, undoStack(), this);
        MainWindow* main_window = MainWindow::getInstance();
        connect(attrib_model, &AttributesModel::rowsInserted, main_window,
                [main_window, attrib_model](const QModelIndex& parent, int first, int last)
        {
            main_window->onAttributesInserted(attrib_model, parent, first, last);
        }, Qt::QueuedConnection);
        connect(attrib_model, &AttributesModel::rowsRemoved, main_window, [main_window, attrib_model](const QModelIndex& parent, int first, int last)
        {
            main_window->onAttributesRemoved(attrib_model, parent, first, last);
        }, Qt::QueuedConnection);
        connect(attrib_model, &AttributesModel::dataChanged, main_window, &MainWindow::onAttributeChanged, Qt::QueuedConnection);
        data = QVariant::fromValue(attrib_model);
        setData(parent_col_0, data, AttributeModelData);
    }
    return data.value<AttributesModel*>();
}

void NodesModel::clearNotUsedAttributesModels()
{
    QMutableHashIterator<MemoryNode*, QVariant> it(attribute_models);
    while(it.hasNext())
    {
        it.next();
        MemoryNode* key = it.key();
        if(!getIndex(key).isValid())
        {
            AttributesModel* attr_model = it.value().value<AttributesModel*>();
            attr_model->deleteLater();
            it.remove();
        }
    }
}

bool NodesModel::isValid(const QModelIndex &index) const
{
    MemoryNode* node = getItem(index);
    return node->isValid();
}

bool NodesModel::isEditable(const QModelIndex &index) const
{
    if(!index.isValid())
        return true;
    const MemoryNode* node = getItem(index);
    return node->isEditable();
}

NodesModel *NodesModel::getNodesModel(const QModelIndex &index)
{
    return const_cast<NodesModel*>(static_cast<const NodesModel*>(index.model()));
}

QModelIndex NodesModel::getIndex(MemoryNode *node) const
{
    const QModelIndexList& list = match(index(0, 0), InternalPointer, QVariant::fromValue(static_cast<void*>(node)), 1, Qt::MatchFlags(Qt::MatchRecursive));
    if(list.empty())
        return QModelIndex();
    return list[0].siblingAtColumn(0);
}

QModelIndexList NodesModel::getIndexes(MemoryNode *node) const
{
    return getIndexes(node, index(0, 0));
}

QModelIndexList NodesModel::getIndexes(MemoryNode *node, const QModelIndex &start) const
{
    return match(start, InternalPointer, QVariant::fromValue(static_cast<void*>(node)), -1, Qt::MatchFlags(Qt::MatchRecursive));
}

void NodesModel::getAllChildren(const QModelIndex& index, QModelIndexList &index_list) const
{
    index_list.push_back(index);
    if(!hasChildren(index))
        return;
    const int rows = rowCount(index);
    for(int i=0; i<rows; ++i)
        getAllChildren(this->index(i, 0, index), index_list);
}

MemoryNode *NodesModel::getRoot()
{
    return rootItem.get();
}

bool NodesModel::isModified(const QModelIndex &index, bool recursive) const
{
    if(_undoStack->isClean())
        return false;
    const int clean_index = _undoStack->cleanIndex();
    const int stack_index = _undoStack->index();
    int start, stop;
    if(clean_index < stack_index)
    {
        start = clean_index;
        stop = stack_index;
    }
    else
    {
        start = stack_index;
        stop = clean_index;
    }
    for(int i=start; i<=stop; ++i)
    {
        const QUndoCommand* undo_command = _undoStack->command(i);
        if(checkCommand(index, undo_command, recursive))
            return true;
    }
    return false;
}

std::vector<QModelIndex> NodesModel::getModifiedIndexes() const
{
    std::vector<QModelIndex> modified_indexes;
    if(_undoStack->isClean())
        return modified_indexes;
    const int stack_index = _undoStack->index();
    const int clean_index = _undoStack->cleanIndex();
    int start, stop;
    if(clean_index < stack_index)
    {
        start = clean_index;
        stop = stack_index;
    }
    else
    {
        start = stack_index;
        stop = clean_index;
    }
    for(int i=start; i<=stop; ++i)
    {
        const QUndoCommand* undo_command = _undoStack->command(i);
        populateModifiedIndexes(undo_command, modified_indexes);
    }
    return modified_indexes;
}
void NodesModel::populateModifiedIndexes(const QUndoCommand* command, std::vector<QModelIndex> &indexes) const
{
    if(!command)
        return;
    // try to cast
    const NodeChangedBaseCommand* node_changed_command = dynamic_cast<const NodeChangedBaseCommand*>(command);
    if(!node_changed_command)
    {
        // probably a macro
        // lets check children
        const int child_count = command->childCount();
        for(int i=0; i<child_count; ++i)
        {
            populateModifiedIndexes(command->child(i), indexes);
        }
    }
    else
    {
        // cast success
        for(const auto& mod_index: node_changed_command->node_index_list)
        {
            indexes.push_back(mod_index);
        }
    }
}

bool NodesModel::checkCommand(const QModelIndex &index, const QUndoCommand *command, bool recursive) const
{
    if(!command)
        return false;
    // try to cast
    const NodeChangedBaseCommand* node_changed_command = dynamic_cast<const NodeChangedBaseCommand*>(command);
    if(!node_changed_command)
    {
        // probably a macro
        // lets check children
        const int child_count = command->childCount();
        for(int i=0; i<child_count; ++i)
        {
            if(checkCommand(index, command->child(i), recursive))
                return true;
        }
    }
    else
    {
        // cast success
        for(const auto& mod_index: node_changed_command->node_index_list)
        {
            if(index == mod_index || (recursive && hasChild(index, mod_index)))
                return true;
        }
    }
    return false;
}

bool NodesModel::hasChild(const QModelIndex &parent, const QModelIndex &child) const
{
    const QModelIndexList& list = match(index(0, 0, parent),
                                        InternalPointer,
                                        QVariant::fromValue(static_cast<void*>(getItem(child))),
                                        1,
                                        Qt::MatchFlags(Qt::MatchRecursive));
    return !list.empty();
}

bool NodesModel::isParent(const QModelIndex &parent, const QModelIndex &child) const
{
    QModelIndex tmp_parent = child.parent();
    while(tmp_parent.isValid())
    {
        if(parent == tmp_parent)
            return true;
        tmp_parent = tmp_parent.parent();
    }
    return false;
}

void NodesModel::save(const QModelIndex &root, const QString &filename) const
{
    const MemoryNode * item = getItem(root);
    if(item)
        item->save(filename.toStdString());
}

void NodesModel::validate(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
{
    if(!roles.empty())
    {
        return;
    }
    QItemSelection selection(topLeft, bottomRight);
    const QModelIndexList& indexes = selection.indexes();
    for(const auto& index: indexes)
    {
        validate(index, false);
    }
}

void NodesModel::validate(const QModelIndex &parent, int first, int last)
{
    if(parent.column() == 0)
    {
        validate(parent, false);
        QItemSelection selection(index(first, 0, parent), index(last, 0, parent));
        const QModelIndexList& indexes = selection.indexes();
        for(const auto& index: indexes)
        {
            validate(index, true);
        }
    }
}

void NodesModel::validate(const QModelIndex &index, const bool recursive)
{
    if(index.isValid() && index.column() == 0)
    {
        // validate only column 0
        MemoryNode* node = getItem(index);
        if(node)
            node->validate(recursive);
    }
}

bool NodesModel::addChild(int position, const QString type, const QModelIndex &parent)
{
    return insertChild(position, std::make_unique<MemoryNode>(type.toStdString()), parent);
}

bool NodesModel::duplicateChild(int targetPosition, const QModelIndex &source_index)
{
    return duplicateChild(targetPosition, source_index, source_index.parent());
}

bool NodesModel::duplicateChild(int targetPosition, const QModelIndex &source_index, const QModelIndex& target_index)
{
    MemoryNode * donor = getItem(source_index);
    return insertChild(targetPosition, std::make_unique<MemoryNode>(*donor), target_index);
}

bool NodesModel::insertChild(int position, std::unique_ptr<MemoryNode> child, const QModelIndex &parent)
{
    if(position < 0 || position > rowCount(parent))
        return false;
    MemoryNode* parent_node = getItem(parent);
    beginInsertRows(parent.siblingAtColumn(0), position, position);
    parent_node->addChild(std::move(child), position);
    endInsertRows();
    return true;
}

bool NodesModel::insertChild(int position, MemoryNode *child, const QModelIndex &parent)
{
    return insertChild(position, std::make_unique<MemoryNode>(*child), parent);
}

std::unique_ptr<MemoryNode> NodesModel::removeChild(int row, const QModelIndex &parent)
{
    MemoryNode* parentItem = getItem(parent);
    if(row < 0 || row > parentItem->childCount())
        return nullptr;
    beginRemoveRows(parent.siblingAtColumn(0), row, row);
    std::unique_ptr<MemoryNode> node = parentItem->removeChild(row);
    endRemoveRows();
    return node;
}

bool NodesModel::moveRows(const QModelIndex &sourceParent, int sourcePosition, int sourceCount, const QModelIndex &destinationParent, int destPosition)
{
    Q_ASSERT(sourceParent.column() == 0);
    Q_ASSERT(destinationParent.column() == 0);
    // get the children to move from the source
    MemoryNode* parentItem = getItem(sourceParent);
    MemoryNode* targetItem = getItem(destinationParent);

    if(!(sourceCount && sourcePosition < parentItem->childCount() && sourcePosition+sourceCount <= parentItem->childCount() && destPosition <= targetItem->childCount() &&
        ((parentItem != targetItem) || (parentItem == targetItem && sourcePosition != destPosition && sourcePosition != destPosition-1)) &&
        sourcePosition >= 0 &&
        destPosition >= 0)
        )
        return false;

    const int sourceLast = sourcePosition+sourceCount-1;
    //qDebug() << "move pos:" << sourcePosition << " count: " << sourceCount << " last: " << sourceLast << "dest: " << destPosition;
    if(!beginMoveRows(sourceParent, sourcePosition, sourceLast, destinationParent, destPosition))
    {
        //qDebug() << "beginMoveRows failed!";
        return false;
    }
    parentItem->moveChildren(sourcePosition, sourceCount, targetItem, destPosition);
    endMoveRows();
    return true;
}

bool NodesModel::moveRows(const QModelIndex &sourceParent, int sourcePosition, const QModelIndex &destinationParent, int destPosition)
{
    return moveRows(sourceParent, sourcePosition, 1, destinationParent, destPosition);
}

bool NodesModel::moveRows(const QModelIndex &sourceParent, const QModelIndex &sourceIndex, const QModelIndex &destinationParent, const QModelIndex &destinationIndex)
{
    return moveRows(sourceParent, sourceIndex.row(), 1, destinationParent, destinationIndex.row());
}


Qt::ItemFlags NodesModel::flags(const QModelIndex &index) const
{
    if(!index.isValid())
        return Qt::ItemIsDropEnabled;
    Qt::ItemFlags default_flags = Qt::ItemIsDragEnabled | Qt::ItemIsSelectable | QAbstractItemModel::flags(index);
    if(isEditable(index))
        default_flags |= Qt::ItemIsDropEnabled;
    return default_flags;
}

Qt::DropActions NodesModel::supportedDropActions() const
{
    return Qt::MoveAction | Qt::CopyAction;
}

Qt::DropActions NodesModel::supportedDragActions() const
{
    return Qt::MoveAction | Qt::CopyAction;
}

QStringList NodesModel::mimeTypes() const
{
    return QStringList() << mime_type;
}

QMimeData *NodesModel::mimeData(const QModelIndexList &indexes, Qt::DropAction action) const
{
    // use only indexes with column 0
    QModelIndexList only_col_0_indexes;
    std::copy_if(std::make_move_iterator(indexes.begin()),
                 std::make_move_iterator(indexes.end()),
                 std::back_inserter(only_col_0_indexes),
                 [](const QModelIndex& index){return index.column() == 0;});

    QMimeData* mimeData = new QMimeData;
    QByteArray data;
    QDataStream stream(&data, QIODevice::WriteOnly);
    stream << static_cast<qlonglong>(action);
    stream << QCoreApplication::applicationPid();
    stream << only_col_0_indexes.count();
    for(const QModelIndex& index: only_col_0_indexes)
    {
        stream << reinterpret_cast<qlonglong>(getItem(index));
//        YAML::Emitter out;
//        YAML::Node node(*getItem((index)));
//        out << node;
//        stream << out.c_str();
        //stream << QPersistentModelIndex(index);
    }
    mimeData->setData(mime_type, data);
    return mimeData;
}

QMimeData* NodesModel::mimeData(const QModelIndexList &indexes) const
{
    return mimeData(indexes, Qt::MoveAction);
}
bool NodesModel::dropMimeData(const QMimeData* mime_data, Qt::DropAction action, int row, int column, const QModelIndex & parent)
{
    Q_ASSERT(action == Qt::MoveAction || action == Qt::CopyAction);
    Q_UNUSED(column);
    if(!mime_data->hasFormat(mime_type))
        return false;
    MemoryNode* parent_node = getItem(parent);
    QByteArray data = mime_data->data(mime_type);
    QDataStream stream(&data, QIODevice::ReadOnly);

    qlonglong int_action_from_mime;
    stream >> int_action_from_mime;

    Qt::DropAction action_from_mime = static_cast<Qt::DropAction>(int_action_from_mime);
    if(action_from_mime != Qt::MoveAction && action_from_mime != Qt::CopyAction)
        return false;

    QString macro_action;
    // get the action to perform
    // default action is Move, but one in mime can be different - it means copy
    if (action == Qt::MoveAction && action_from_mime == Qt::MoveAction)
    {
        action = Qt::MoveAction;
        macro_action = "Node drag&drop";
    }
    else if (action == Qt::CopyAction || action_from_mime == Qt::CopyAction)
    {
        action = Qt::CopyAction;
        macro_action = "Node copy&paste";
    }
    else
    {
        // not handled
        return false;
    }
    // only move or copy is handled
    Q_ASSERT(action == Qt::MoveAction || action == Qt::CopyAction);

    if(parent_node == rootItem.get()) // root item, no validator
        return false;

    qint64 appPID;
    stream >> appPID;
    if(appPID != QCoreApplication::applicationPid())
        return false;
    int count;
    stream >> count;
    if(row == -1)
    {
        if(parent.isValid())
            row = 0;
        else
            row = rowCount(parent);
    }
    // read all nodes
    std::vector<MemoryNode*> nodes;
    for(int i=0; i<count; ++i)
    {
        qlonglong node_ptr;
        stream >> node_ptr;
        MemoryNode* node = reinterpret_cast<MemoryNode*>(node_ptr);
        nodes.push_back(node);
    }
    // validate nodes
    for(const auto& node: nodes)
    {
        const QModelIndex& index = getIndex(node);

        if(!index.isValid())
            return false;
        if(!parent_node->getValidator()->isAllowed(node->getType()) || node->isSpecialSequence())
        {
            // not allowed children or special sequence
            return false;
        }
    }
    if(action == Qt::MoveAction)
    {
        // drag & drop
        // must come from the same parent and be continuous area

        // check if all of them have the same parent
        {
            MemoryNode* parent = nodes.front()->getParent();
            const auto& result = std::find_if(nodes.begin()+1, nodes.end(), [&parent](const auto& n){return n->getParent() != parent;});
            if(result != nodes.end())
                return false;
        }
        // check if they're continuous
        // NOT NEEDED, since the selection mode has been changed to continuous
//        {
//            MemoryNode* front = nodes.front();
//            MemoryNode* back = nodes.back();

//            const QModelIndex& front_index = getIndex(front);
//            const QModelIndex& back_index = getIndex(back);

//            QItemSelectionRange range(front_index, back_index);
//            if(!range.isValid())
//                return false;
//        }

    }

    if(action == Qt::CopyAction)
    {
        _undoStack->beginMacro(macro_action);
        for(const auto& node: nodes)
        {
            const QModelIndex& source_index = getIndex(node);
            _undoStack->push(new DuplicateNodeCommand(source_index, row+1, parent));
            ++row;
        }
        _undoStack->endMacro();
    }
    else if (action == Qt::MoveAction)
    {
        const QModelIndex& sourceIndex = getIndex(nodes.front());
        const QModelIndex& sourceParent = sourceIndex.parent();
        _undoStack->push(new DragNodeCommand(sourceParent, sourceIndex.row(), static_cast<int>(nodes.size()), parent, row));
    }
    else
    {
        return false;
    }
    return true;
}

